/**
 * 这个类封装了所有Three.js中的建立或删除操作的代码，比如创建渲染器、顶层场景和相机。
 * 同时还加入了处理DOM事件的部分，可以控制画布的缩放、鼠标输入和其他事件。
 * 同时管理应用中不同对象的列表，还负责循环重绘
 */

import * as THREE from 'three';
import { PerspectiveCamera, Scene, WebGLRenderer, AmbientLight, Object3D, Raycaster, Vector3, Intersection, Matrix4, Face3 } from "three";
import Publisher from './Publisher';
import $ from 'jquery';
import SimObject from './SimObject';

export default class Application extends Publisher {

    public renderer!: WebGLRenderer;
    public scene!: Scene;
    public camera!: PerspectiveCamera;
    public container: HTMLElement;
    public root: Object3D;
    public objects: SimObject[] = [];
    // 点击选择
    public raycaster: Raycaster;

    public overObject: any;
    public clickedObject: any;

    public constructor(param: any) {
        super();
        param = param || {};
        let container: HTMLElement = param.container;
        let canvas: HTMLCanvasElement = param.canvas;        

        // 创建一个Three.js的WebGL渲染器，并且添加到一个div上
        let renderer: WebGLRenderer = new WebGLRenderer({
            antialias: true,
            canvas: canvas
        });
        renderer.setSize(container.offsetWidth, container.offsetHeight);
        container.appendChild(renderer.domElement);

        // 创建场景
        let scene: Scene = new Scene();
        scene.add(new AmbientLight(0x505050));
        scene.userData = this;
        // scene.data = this;

        this.camera = new PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 1, 10000);
        this.camera.position.set(0, 0, 3.3333);

        scene.add(this.camera);

        let root: Object3D = new Object3D();
        scene.add(root);

        this.raycaster = new Raycaster();

        this.container = container;
        this.renderer = renderer;
        this.scene = scene;
        this.root = root;
    }

    public run(): void {
        this.update();
        this.renderer.render(this.scene, this.camera);
        requestAnimationFrame(this.run.bind(this));
    }

    public update(): void {
        let i: number, len: number;
        len = this.objects.length;
        for (i = 0; i < len; i++) {
            this.objects[i].update();
        }
    }

    public addObject(obj: SimObject): void {
        this.objects.push(obj);
        if (obj.object3D) {
            this.root.add(obj.object3D);
        }
    }

    public removeObject(obj: SimObject): void {
        let index: number = this.objects.indexOf(obj);
        if (index !== -1) {
            this.objects.splice(index, 1);
            if (obj.object3D) {
                this.root.remove(obj.object3D);
            }
        }
    }

    public initMouse(): void {
        const dom: HTMLElement = this.renderer.domElement;

        // dom.addEventListener('mousemove', this.onDocumentMouseMove, false);
        // dom.addEventListener('mousedown', this.onDocumentMouseDown, false);
        // dom.addEventListener('mouseup', this.onDocumentMouseUp, false);
        // dom.addEventListener('scroll', this.onDocumentMouseScroll, false);
        this.overObject = null;
        this.clickedObject = null;
    }

    public initKeyboard(): void {
        const dom: HTMLElement = this.renderer.domElement;
        dom.addEventListener('keydown', this.onKeyDown, false);
        dom.addEventListener('keyup', this.onKeyUp, false);
        dom.addEventListener('keypress', this.onKeyPress, false);

        dom.setAttribute("tabindex", "1");
        dom.style.outline = 'none';
    }

    private addDomHandlers(): void {
        window.addEventListener('resize', this.onWindowResize, false);
    }

    private onDocumentMouseMove(event: MouseEvent): void {
        event.preventDefault();
        if (this.clickedObject && this.clickedObject.handleMouseMove) {
            var hitpoint = null, hitnormal = null;
            var intersected = this.objectFromMouse(event.pageX, event.pageY);
            if (intersected.object == this.clickedObject) {
                hitpoint = intersected.point;
                hitnormal = intersected.normal;
            }
            this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
        } else {
            var handled = false;

            var oldObj = this.overObject;
            var intersected = this.objectFromMouse(event.pageX, event.pageY);
            this.overObject = intersected.object;

            if (this.overObject != oldObj) {
                if (oldObj) {
                    this.container.style.cursor = 'auto';

                    if (oldObj.handleMouseOut) {
                        oldObj.handleMouseOut(event.pageX, event.pageY);
                    }
                }

                if (this.overObject) {
                    if (this.overObject.overCursor) {
                        this.container.style.cursor = this.overObject.overCursor;
                    }

                    if (this.overObject.handleMouseOver) {
                        this.overObject.handleMouseOver(event.pageX, event.pageY);
                    }
                }

                handled = true;
            }

            // if (!handled && this.handleMouseMove) {
            //     this.handleMouseMove(event.pageX, event.pageY);
            // }
        }
    }

    private onDocumentMouseDown(event: MouseEvent): void {
        event.preventDefault();

        let handled = false;

        // var intersected = this.objectFromMouse(event.pageX, event.pageY);
        // if (intersected.object) {
        //     if (intersected.object.handleMouseDown) {
        //         intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal);
        //         this.clickedObject = intersected.object;
        //         handled = true;
        //     }
        // }

        // if (!handled && this.handleMouseDown) {
        //     this.handleMouseDown(event.pageX, event.pageY);
        // }
    }

    private onDocumentMouseUp(event: MouseEvent): void {
        event.preventDefault();

        let handled = false;

        // var intersected = this.objectFromMouse(event.pageX, event.pageY);
        // if (intersected.object) {
        //     if (intersected.object.handleMouseUp) {
        //         intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal);
        //         handled = true;
        //     }
        // }

        // if (!handled && this.handleMouseUp) {
        //     this.handleMouseUp(event.pageX, event.pageY);
        // }

        this.clickedObject = null;
    }

    private onDocumentMouseScroll(event: MouseEvent): void {

    }


    private onKeyDown(event: KeyboardEvent): void {

    }

    private onKeyUp(event: KeyboardEvent): void {

    }

    private onKeyPress(evt: KeyboardEvent): void {
        evt.preventDefault();
        /* if(this.handleKeyPress){
            this.handleKeyPress(evt.keyCode, evt.charCode);
        } */
    }

    private objectFromMouse(pagex: number, pagey: number): any {
        let offsetLeft: number = this.renderer.domElement.offsetLeft;
        let offsetTop: number = this.renderer.domElement.offsetTop;
        let eltx: number = pagex - offsetLeft;
        let elty: number = pagey - offsetTop;

        let vpx: number = (eltx / this.container.offsetWidth) * 2 - 1;
        let vpy: number = -(elty / this.container.offsetHeight) * 2 + 1;

        let vector: Vector3 = new Vector3(vpx, vpy, 0.5);
        // 通过摄像机和鼠标位置更新射线
        this.raycaster.setFromCamera(vector, this.camera);
        // 计算物体和射线的焦点
        let intersects: Intersection[] = this.raycaster.intersectObjects(this.scene.children);
        if (intersects.length > 0) {
            let i = 0;
            while (!intersects[i].object.visible) {
                i++;
            }
            let intersected: Intersection = intersects[i] as Intersection;
            let mat: Matrix4 = new Matrix4().getInverse(intersected.object.matrixWorld);
            let point: Vector3 = mat.multiplyVector3(intersected.point);
            return this.findObjectFromIntersected(intersected.object, intersected.point, (intersected.face as Face3).normal);
        } else {
            return { object: null, point: null, normal: null };
        }
    }

    private findObjectFromIntersected(object: Object3D, point: Vector3, normal: Vector3): any {
        if (object.userData) {
            return { object: object.userData, point: point, normal: normal };
        } else if (object.parent) {
            return this.findObjectFromIntersected(object.parent, point, normal);
        } else {
            return { objec: null, point: null, normal: null };
        }
    }

    /**
     * 浏览器窗口缩放触发事件
     * @param evt 
     */
    public onWindowResize(evt: Event): void {
        this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
        this.camera.aspect = this.container.offsetWidth / this.container.offsetHeight;
        this.camera.updateProjectionMatrix();
    }

    /**
     * 设置domElement获得鼠标焦点
     */
    public focus(): void {
        if (this.renderer && this.renderer.domElement) {
            this.renderer.domElement.focus();
        }
    }

}